/**
 * 
 */
package neptune.manager.storage;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import neptune.manager.model.cluster.Cluster;
import neptune.manager.model.cluster.PCU;
import neptune.manager.model.cluster.PhysicalNode;
import neptune.manager.model.topology.Cpu;
import neptune.manager.model.topology.IPAddress;
import neptune.manager.model.topology.MACAddress;
import neptune.manager.model.topology.NetworkInterface;
import neptune.manager.model.topology.OperativeSystem;
import neptune.manager.model.topology.Software;
import neptune.manager.model.topology.UndefinedControlInterfaceException;
import neptune.manager.utils.IllegalValueException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
 * @author Roberto Bifulco [info@robertobifulco.it, robertobifulco.it]
 * 
 */
public class XMLClusterStorage implements ClusterStorage {

	private File xmlFile;

	/**
	 * @param xmlFile
	 */
	public XMLClusterStorage(File xmlFile) {
		this.xmlFile = xmlFile;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see neptune.manager.storage.ClusterStorage#getCluster()
	 */
	@Override
	public Cluster getCluster() throws StorageReadException {
		Cluster cluster = new Cluster();

		DocumentBuilderFactory docFactory = DocumentBuilderFactory
				.newInstance();
		docFactory.setValidating(false); // La validazione e' intesa solo in
		// riferimento ai DTD

		// TODO inserire riferimento a XML SCHEMA
		// docFactory.setSchema(schema)

		try {
			Document doc = docFactory.newDocumentBuilder().parse(xmlFile);

			Element clusterEl = doc.getDocumentElement();

			NodeList nodes = clusterEl.getElementsByTagName("machine");
			PhysicalNode[] clusterNodes = new PhysicalNode[nodes.getLength()];

			for (int i = 0; nodes != null && i < nodes.getLength(); i++) {
				try {
					PhysicalNode node = buildNodeFromElement((Element) nodes
							.item(i));
					clusterNodes[i] = node;
				} catch (IllegalValueException e) {
					throw new StorageReadException(e);
				}
			}

			cluster.setNodes(clusterNodes);

		} catch (SAXException e) {
			throw new StorageReadException(e);
		} catch (IOException e) {
			throw new StorageReadException(e);
		} catch (ParserConfigurationException e) {
			throw new StorageReadException(e);
		}

		return cluster;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * neptune.manager.storage.ClusterStorage#saveCluster(neptune.manager.model
	 * .cluster.Cluster)
	 */
	@Override
	public void saveCluster(Cluster cluster) throws StorageSaveException {
		String xmlString = getXML(cluster);

		try {
			xmlFile.createNewFile();
			FileWriter writer = new FileWriter(xmlFile);
			writer.write(xmlString);
			writer.flush();
		} catch (IOException e) {
			throw new StorageSaveException(e);
		}
	}

	public String getXML(Cluster cluster) throws StorageSaveException {
		DocumentBuilderFactory docFactory = DocumentBuilderFactory
				.newInstance();
		docFactory.setValidating(false);

		try {
			Document document = docFactory.newDocumentBuilder().newDocument();
			document.setXmlVersion("1.0");

			Element root = document.createElement("cluster");
			document.appendChild(root);

			createMachinesElement(document, cluster, root);

			// ///////////////
			// Output the XML

			// set up a transformer
			TransformerFactory transfac = TransformerFactory.newInstance();
			Transformer trans = transfac.newTransformer();
			trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
			trans.setOutputProperty(OutputKeys.INDENT, "yes");
			trans.setOutputProperty(OutputKeys.STANDALONE, "yes");
			trans.setOutputProperty(
					"{http://xml.apache.org/xslt}indent-amount", "2");

			// create string from xml tree
			StringWriter sw = new StringWriter();
			StreamResult result = new StreamResult(sw);
			DOMSource source = new DOMSource(document);
			trans.transform(source, result);
			return sw.toString();

		} catch (ParserConfigurationException e) {
			throw new StorageSaveException(e);
		} catch (TransformerException e) {
			throw new StorageSaveException(e);
		}
	}

	private void createMachinesElement(Document doc, Cluster cluster,
			Element nodesEl) {
		if (cluster.getNodes() == null)
			return;
		for (PhysicalNode node : cluster.getNodes()) {
			Element nodeEl = doc.createElement("machine");
			nodeEl.setAttribute("id", node.getId());
			nodesEl.appendChild(nodeEl);

			Element description = doc.createElement("description");
			description.setTextContent(node.getDescription());
			nodeEl.appendChild(description);

			Element name = doc.createElement("name");
			name.setTextContent(node.getName());
			nodeEl.appendChild(name);

			if (node.getCpu() != null) {
				Element cpu = doc.createElement("cpu");
				cpu.setAttribute("name", node.getCpu().getName());
				cpu.setAttribute("type", node.getCpu().getType());
				cpu.setAttribute("clock", Integer.toString(node.getCpu()
						.getClock()));
				cpu.setAttribute("amount", Integer.toString(node.getCpu()
						.getAmount()));
				nodeEl.appendChild(cpu);
			}

			Element ram = doc.createElement("ram");
			ram.setAttribute("dimension", Integer.toString(node.getRam()));
			nodeEl.appendChild(ram);

			Element hd = doc.createElement("hd");
			hd.setAttribute("dimension", Float.toString(node.getHd()));
			nodeEl.appendChild(hd);

			if (node.getPcu() != null) {
				Element pcu = doc.createElement("pcu");
				pcu.setAttribute("type", node.getPcu().getType());
				pcu.setAttribute("ipVersion", Integer.toString(node.getPcu()
						.getIpAddress().getVersion()));
				pcu.setAttribute("ipAddress", node.getPcu().getIpAddress()
						.getAddress());
				pcu.setAttribute("subnetMask", node.getPcu().getIpAddress()
						.getSubnetMask());
				pcu.setAttribute("username", node.getPcu().getUsername());
				pcu.setAttribute("password", node.getPcu().getPassword());
				nodeEl.appendChild(pcu);
			}

			Element networkIfs = doc.createElement("network_interfaces");
			nodeEl.appendChild(networkIfs);

			for (NetworkInterface netIf : node.getInterfaces()) {
				if (netIf == null)
					break;

				Element netIfEl = doc.createElement("interface");
				networkIfs.appendChild(netIfEl);
				netIfEl.setAttribute("name", netIf.getInterfaceName());
				netIfEl.setAttribute("MAC",
						netIf.getMacAddress() != null ? netIf.getMacAddress()
								.getAddress() : null);
				netIfEl.setAttribute("type", netIf.getType());

				Element ifBinding = doc.createElement("interface_binding");
				ifBinding.setAttribute("type", netIf.getBindingDescription());
				netIfEl.appendChild(ifBinding);

				for (IPAddress ipAddr : netIf.getIpAddresses()) {
					Element ipAddrEl = doc.createElement("ip");
					ipAddrEl.setAttribute("version", Integer.toString(ipAddr
							.getVersion()));
					ipAddrEl.setAttribute("address", ipAddr.getAddress());
					ipAddrEl.setAttribute("subnetMask", ipAddr.getSubnetMask());
					ipAddrEl.setAttribute("type",
							ipAddr.isControlAddress() ? "control" : "topology");
					netIfEl.appendChild(ipAddrEl);
				}
			}

			if (node.getOperativeSystem() != null) {
				Element os = doc.createElement("operative_system");
				os.setAttribute("name", node.getOperativeSystem().getName());
				os.setAttribute("version", node.getOperativeSystem()
						.getVersion());
				os.setAttribute("username", node.getOperativeSystem()
						.getAdminUsername());
				os.setAttribute("password", node.getOperativeSystem()
						.getAdminPassword());
				nodeEl.appendChild(os);
			}

			Element softConf = doc.createElement("software_configuration");
			softConf.setAttribute("template", node.getSoftwareTemplate());
			nodeEl.appendChild(softConf);

			Element sshElement = doc.createElement("sshServer");
			sshElement.setAttribute("port", Integer.toString(node
					.getControlPort()));
			softConf.appendChild(sshElement);

			for (Software soft : node.getInstalledSoftwares()) {
				Element softEl = doc.createElement("software");
				softEl.setAttribute("name", soft.getId());
				softEl.setAttribute("type", soft.getType());
				softEl.setAttribute("version", soft.getVersion());
				softConf.appendChild(softEl);
			}

			if (node.getHypervisor() != null) {
				Element hypervisor = doc.createElement("hypervisor");
				hypervisor.setAttribute("name", node.getHypervisor().getType());
				hypervisor.setAttribute("version", node.getHypervisor()
						.getVersion());
				nodeEl.appendChild(hypervisor);
			}

			Element hostedVMsEl = doc.createElement("hosted_VMs");
			nodeEl.appendChild(hostedVMsEl);

			for (String vmId : node.getHostedNodes()) {
				Element vm = doc.createElement("vm");
				vm.setAttribute("id", vmId);
				hostedVMsEl.appendChild(vm);
			}
		}
	}

	private PhysicalNode buildNodeFromElement(Element item)
			throws IllegalValueException {

		NodeList descriptionNL = item.getElementsByTagName("description");
		String description = "";
		if (descriptionNL.item(0) != null)
			description = descriptionNL.item(0).getTextContent();

		NodeList nameNL = item.getElementsByTagName("name");
		String name = "";
		if (nameNL.item(0) != null)
			name = nameNL.item(0).getTextContent();

		PhysicalNode node = new PhysicalNode(description, item
				.getAttribute("id"), name);

		NodeList itemChilds = item.getChildNodes();
		for (int i = 0; i < itemChilds.getLength(); i++) {
			if (!itemChilds.item(i).getNodeName().equals("#text")) {

				String nodeName = itemChilds.item(i).getNodeName();

				if (nodeName.equals("cpu")) {

					Element cpuEl = (Element) itemChilds.item(i);
					Cpu cpu = new Cpu(cpuEl.getAttribute("name"), cpuEl
							.getAttribute("type"), Integer.parseInt(cpuEl
							.getAttribute("clock")), Integer.parseInt(cpuEl
							.getAttribute("amount")));
					node.setCpu(cpu);

				} else if (nodeName.equals("ram")) {

					Element ramEl = (Element) itemChilds.item(i);
					node.setRam(Integer.parseInt(ramEl
							.getAttribute("dimension")));

				} else if (nodeName.equals("hd")) {

					Element hdEl = (Element) itemChilds.item(i);
					node
							.setHd(Float.parseFloat(hdEl
									.getAttribute("dimension")));

				} else if (nodeName.equals("pcu")) {

					Element pcuEl = (Element) itemChilds.item(i);
					node.setPcu(new PCU(pcuEl.getAttribute("type"),
							new IPAddress(Integer.parseInt(pcuEl
									.getAttribute("ipVersion")), pcuEl
									.getAttribute("ipAddress"), pcuEl
									.getAttribute("subnetMask"), true), pcuEl
									.getAttribute("username"), pcuEl
									.getAttribute("password")));

				} else if (nodeName.equals("network_interfaces")) {

					NodeList interfaces = ((Element) itemChilds.item(i))
							.getElementsByTagName("interface");
					for (int j = 0; j < interfaces.getLength(); j++) {
						NetworkInterface netInterface = buildInterfaceFromElement((Element) interfaces
								.item(j));
						node.addNetworkInterface(netInterface);
					}

				} else if (nodeName.equals("operative_system")) {

					Element osEl = (Element) itemChilds.item(i);
					OperativeSystem os = new OperativeSystem(osEl
							.getAttribute("name"),
							osEl.getAttribute("version"), osEl
									.getAttribute("username"), osEl
									.getAttribute("password"));
					node.setOperativeSystem(os);

				} else if (nodeName.equals("software_configuration")) {

					Element softwareConfigurationEl = (Element) itemChilds
							.item(i);
					node.setSoftwareTemplate(softwareConfigurationEl
							.getAttribute("template"));

					Element sshServer = (Element) softwareConfigurationEl
							.getElementsByTagName("sshServer").item(0);
					node.setControlPort(Integer.parseInt(sshServer
							.getAttribute("port")));

					NodeList softwares = softwareConfigurationEl
							.getElementsByTagName("software");
					for (int j = 0; j < softwares.getLength(); j++) {
						Element softwareEl = (Element) softwares.item(j);
						node.addSoftware(new Software(softwareEl
								.getAttribute("name"), softwareEl
								.getAttribute("version"), softwareEl
								.getAttribute("type")));
					}

				} else if (nodeName.equals("hosted_VMs")) {
					ArrayList<String> vms = new ArrayList<String>(0);
					Element hostedVMs = (Element) itemChilds.item(i);
					NodeList vmEls = hostedVMs.getElementsByTagName("vm");
					for (int j = 0; j < vmEls.getLength(); j++) {
						Element vmEl = (Element) vmEls.item(j);
						vms.add(vmEl.getAttribute("id"));
					}

					node.setHostedNodes(vms);
				}
			}
		}

		return node;
	}

	private NetworkInterface buildInterfaceFromElement(Element item)
			throws IllegalValueException {

		NetworkInterface netInterface = new NetworkInterface(item
				.getAttribute("name"), NetworkInterface.PHYSICAL_INTERFACE,
				new MACAddress(item.getAttribute("MAC")));

		NodeList ips = item.getElementsByTagName("ip");
		for (int i = 0; i < ips.getLength(); i++) {
			Element ipEl = (Element) ips.item(i);
			boolean isControl = false;
			if (ipEl.getAttribute("type").equals("control"))
				isControl = true;
			IPAddress ipAddress = new IPAddress(Integer.parseInt(ipEl
					.getAttribute("version")), ipEl.getAttribute("address"),
					ipEl.getAttribute("subnetMask"), isControl);
			netInterface.addIpAddress(ipAddress);
		}

		return netInterface;
	}

	@Override
	public String[] getPhysicalMachineCredential(String idPhysicalMachine)
			throws StorageReadException {
		String ipAddress = "";
		String password = "";
		String sshPort = "";
		try {
			Cluster cluster = this.getCluster();
			PhysicalNode phyNode = cluster.getNodeById(idPhysicalMachine);
			ipAddress = phyNode.getControlAddress().getAddress();
			OperativeSystem os = phyNode.getOperativeSystem();
			password = os.getAdminPassword();
			sshPort = String.valueOf(phyNode.getControlPort());
		} catch (UndefinedControlInterfaceException e) {
			throw new StorageReadException(e);
		}
		return new String[] { password, sshPort, ipAddress };
	}

	public static void main(String[] args) throws StorageReadException {
		XMLClusterStorage storage = new XMLClusterStorage(new File(
				"/etc/emuPlugin/cluster.xml"));
		String[] credential = storage
				.getPhysicalMachineCredential("1226657151596");
		for (int i = 0; i < credential.length; i++) {
			System.out.println(credential[i]);
		}
	}

}
